(function(angular) {
  'use strict';
  angular.module('fs.date', ['fs.storage', 'fs.format']).factory('dateService', [
    '$filter',
    'storageService',
    'formatService',
    function($filter, storageService, formatService) {
      /**
       * function list
       * 1. isHoliday(nowDate,objHdArry)		//input time is holiday (no Saturday and  no Sunday)
       * 2. getEndDate(nowDate, addnum)		//get maturity date from begin date and tenor (no care about if the maturity date is holiday)
       * 3. getEeffectEndDate(nowDate, addnum,objHdArry)	//get maturity date from begin date and tenor (if the maturity date is holiday then it postpones until it isnot holiday)
       * 4. getDateOffset(beginDate,endDate)  //get tenor between begin date and maturity date (nature day)
       * 5. getEeffectDateOffset(beginDate,endDate,objHdArry)  //get tenor between begin date and maturity date (only workday)
       * 6. isSatOrSunDay(nowDate)   //the date is Saturday or Sunday
       */
      /**
       * @initial month array
       * @params
       * @return
       */
      function _Months() {
        this[0] = 'Jan';
        this[1] = 'Feb';
        this[2] = 'Mar';
        this[3] = 'Apr';
        this[4] = 'May';
        this[5] = 'Jun';
        this[6] = 'Jul';
        this[7] = 'Aug';
        this[8] = 'Sep';
        this[9] = 'Oct';
        this[10] = 'Nov';
        this[11] = 'Dec';
      }
      //define month array
      var mons = new _Months();

      /**
       * @input time is holiday (no Saturday and  no Sunday)
       * @params nowDate - date, formatter is :2003-Jul-29
       * @params objHdArry - holiday array, formatter is :objHdArry[0] = "2003-Jan-01",objHdArry[1] = "2003-Jan-07"...
       * @return true or false
       */
      function isHoliday(nowDate, objHdArry) {
        for (var tmp in objHdArry) {
          if (compareDate(objHdArry[tmp], nowDate) == 0) {
            return true;
          }
        }
        return false;
      }

      /**
       * @ get maturity date from begin date and tenor (no care about if the maturity date is holiday)
       * @param nowDate - date, formatter is :2003-Jul-29 or 2003-01-29
       * @param space - tenor, integer
       * @return String, formatter is :2003-01-29
       */
      function getEndDate(nowDate, space) {
        var curryDate;
        var endDate;

        curryDate = parseDate(nowDate);
        //get maturity date
        space = parseInt(space);
        curryDate.setDate(curryDate.getDate() + space);

        //transform the format
        return formatService.formatDate(curryDate, 'date');
      }

      /**
       * @ get maturity date from begin date and tenor (no care about if the maturity date is holiday)
       * @param nowDate - date, formatter is :2003-Jul-29 or 2003-01-29
       * @param space - tenor, integer
       * @return Date, no format
       */
      function addDays(nowDate, space) {
        var curryDate;
        var endDate;

        curryDate = parseDate(nowDate);
        //get maturity date
        space = parseInt(space);
        curryDate.setDate(curryDate.getDate() + space);

        return curryDate;
      }

      /**
       * @get maturity date from begin date and tenor (if the maturity date is holiday then it postpones until it isnot holiday)
       * @params nowDate - date, formatter is :2003-Jul-29
       * @params addnum - tenor, integer
       * @params objHdArry - date, formatter is :objHdArry[0] = "2003-Jan-01",objHdArry[1] = "2003-Jan-07"...
       * @return: the maturity date that is not holiday, it's formatter is :2003-Jul-29
       */
      //		function getEeffectEndDate(nowDate, addnum,objHdArry){
      //		    var endDate=getEndDate(nowDate, addnum);
      //		    var _addnum=addnum-0;
      //		    while(isHoliday(endDate,objHdArry) || isSatOrSunDay(endDate)){
      //		    	_addnum=_addnum+1;
      //		    	endDate=getEndDate(nowDate, _addnum);
      //		    }
      //		    return endDate;
      //		}

      /**
       * get effective end date
       * @params nowdate - begin date
       * @params addnum - tenor
       * @params objhdarry - holiday array
       * @params weekEnd - weekend array
       * @params direct - forward or backword when the end day is holiday
       */
      function getEeffectEndDate(nowDate, addnum, objHdArry, weekEnd, direct) {
        var endDate = getEndDate(nowDate, addnum);
        var _addnum = addnum - 0;

        //		    while(isHoliday(endDate,objHdArry) || isWeekend(endDate, weekEnd)){
        // objHdArry is date, so need change endDate to date
        while (isHoliday(parse(endDate), objHdArry)) {
          if (direct == 'N') _addnum = _addnum + 1;
          else _addnum = _addnum - 1;
          endDate = getEndDate(nowDate, _addnum);
        }
        return endDate;
      }

      /**
       * @get tenor between begin date and maturity date (nature day)
       * @params beginDate - begin date, formatter is :2003-Jul-29
       * @params endDate - end date, formatter is :2003-Jul-29
       * @return: the number of nature days betwwen beginDate and endDate
       */
      function getDateOffset(beginDate, endDate) {
        var pd1 = parseDate(beginDate);
        var pd2 = parseDate(endDate);
        var MinMilli = 1000 * 60;
        var HrMilli = MinMilli * 60;
        var DyMilli = HrMilli * 24;
        var difference = Date.UTC(pd2.getFullYear(), pd2.getMonth(), pd2.getDate()) - Date.UTC(pd1.getFullYear(), pd1.getMonth(), pd1.getDate());
        return difference / DyMilli;
      }

      /**
       * @get tenor between begin date and maturity date (workday)
       * @params beginDate - begin date, formatter is :2003-Jul-29
       * @params endDate - end date, formatter is :2003-Jul-29
       * @params beginDate - holiday array, formatter is :objHdArry[0] = "2003-Jan-01",objHdArry[1] = "2003-Jan-07"...
       * @return: the number of valid workdays betwwen beginDate and endDate
       */
      function getEeffectDateOffset(beginDate, endDate, objHdArry) {
        var total = getDateOffset(beginDate, endDate);
        var hoilday = 0;
        for (var k = 0; k <= total; k++) {
          if (isHoliday(getEndDate(beginDate, k + 0), objHdArry) || isSatOrSunDay(getEndDate(beginDate, k + 0))) hoilday = hoilday + 1;
        }
        return total - hoilday;
      }

      /**
       * If the date is weekend.
       * @param strDate - String, formater is :"2003-Jul-29" or "2005-10-10"
       * @return boolean
       */
      function isWeekend(strDate) {
        if (!strDate || strDate == '') {
          return false;
        }

        var curryDate = parseDate(strDate);
        if (curryDate == null) {
          return false;
        }
        return curryDate.getDay() == 0 || curryDate.getDay() == 6;
      }

      /**
       *
       * @param strDateStarting String. "2005-10-10"
       * @param strDateEnd String. "2005-10-10"
       * @return int. if value > 0, the starting date is after the end date; if <, the starting date is before the end date. if =, the same date.
       */
      function compareDate(strDateStarting, strDateEnd) {
        var startDate = parse(strDateStarting);
        var endDate = parse(strDateEnd);
        return getDaysBetweenDates(startDate, endDate);
      }

      /**
       *
       * @param strDate String. "2005-10-10"
       * @return Date
       */
      function parseDate(strDate, fmtStr) {
        return parse(strDate, fmtStr);
      }

      /**
       * Get the last day of month which the assigned date is in.
       * @param assignedDate - String. "2005-10-10"
       * @return Date - String. "2005-10-31"
       * @since 2007.5.23
       */
      function getLastDayOfMonth(assignedDate) {
        var dayOfMonth;
        assignedDate = parseDate(assignedDate);
        var month = assignedDate.getMonth() + 1;
        var year = assignedDate.getFullYear();

        // RETURN 31 DAYS
        if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
          dayOfMonth = 31;
        }
        // RETURN 30 DAYS
        else if (month == 4 || month == 6 || month == 9 || month == 11) {
          dayOfMonth = 30;
        }
        // RETURN 29 DAYS
        else if (month == 2) {
          if (isLeapYear(year)) {
            dayOfMonth = 29;
          }
          // RETURN 28 DAYS
          else {
            dayOfMonth = 28;
          }
        }
        //get maturity date
        assignedDate.setDate(dayOfMonth);
        //transform the format
        return formatService.formatDate(assignedDate, 'date');
      }
      /**
       * CHECK TO SEE IF YEAR IS A LEAP YEAR.
       * @param Year - integer.
       * @return true - leap year. false - not leap year
       * @since 2007.5.23
       */
      function isLeapYear(Year) {
        if ((Year % 4 == 0 && Year % 100 != 0) || Year % 400 == 0) {
          return true;
        } else {
          return false;
        }
      }

      function parse(dateStr, fmtStr) {
        if (!dateStr || angular.isDate(dateStr)) return dateStr;
        return formatService.parseDate(dateStr, fmtStr || 'date');
      }

      /**
       * Validate date String with format string.
       * @param dateStr
       * @param fmtStr
       * @return the value true if the date string is legal;
       *		   a value false if this Date is illegal;
       */
      function validate(dateStr, fmtStr) {
        return parse(dateStr, fmtStr) != null;
      }

      /**
       * Compares two dates for equality.
       * @param date1
       * @param date2
       * @return the value true if the argument Date is equal to this Date;
       */
      function compare(date1, date2) {
        if (!date1 || !date2) return null;
        var time1 = date1.getTime();
        var time2 = date2.getTime();
        if (time1 > time2) {
          return -1;
        } else if (time1 < time2) {
          return 1;
        } else {
          return 0;
        }
      }

      /**
       * calculate number of days between two dates, beginDate (included) to endDate (excluded).
       * @param endDate the end date of the period.
       * @param beginDate the begin date of the period
       * @return number of days from beginDate (included) to endDate (excluded)
       */
      function getDaysBetweenDates(beginDate, endDate) {
        if (!beginDate || !endDate) return null;
        var beginTime = beginDate.getTime();
        var endTime = endDate.getTime();
        var oneDay = 24 * 60 * 60 * 1000;
        return Math.ceil((endTime - beginTime) / oneDay);
      }

      /**
       * Compares two dates for equality.
       * @param date1
       * @param date2
       * @return the value true if the argument Date is equal to this Date;
       */
      function equals(date1, date2) {
        if (!date1 || !date2) return false;
        return date1.getTime() == date2.getTime();
      }

      /**
       * Compares two dates for equality. ignoring time considerations.
       * @param date1
       * @param date2
       * @return the value true if the argument Date is equal to this Date;
       * @see #equals(date1, date2);
       */
      function equalsIgnoreTime(date1, date2) {
        //Clear the date. set hour, minute, second and millisecond to 0.
        this.clearTime = function(targetDate) {
          targetDate.setHours(0);
          targetDate.setMinutes(0);
          targetDate.setSeconds(0);
          targetDate.setMilliseconds(0);
          return targetDate;
        };
        var date1 = this.clearTime(date1);
        var date2 = this.clearTime(date2);
        return equals(date1, date2);
      }

      /**
       * Date Arithmetic function. Adds the specified (signed) amount of time to the given time field.
       * For example:  add(new Date(), 'M', -5)
       *				 add(new Date(), 'y', 3)
       * @param date
       * @param type
       * @param interval
       * @return date;
       */
      function add(date, type, interval) {
        if (!date || typeof date != 'object') return null;
        if (!type || !interval) return date;
        interval = parseInt(interval);
        if (type == 'y') {
          date.setFullYear(date.getFullYear() + interval);
        } else if (type == 'M') {
          date.setMonth(date.getMonth() + interval);
        } else if (type == 'd') {
          date.setDate(date.getDate() + interval);
        } else if (type == 'h') {
          date.setHours(date.getHours() + interval);
        } else if (type == 'm') {
          date.setMinutes(date.getMinutes() + interval);
        } else if (type == 's') {
          date.setSeconds(date.getSeconds() + interval);
        }
        return date;
      }

      return {
        isHoliday: isHoliday,
        getEndDate: getEndDate,
        addDays: addDays,
        getEeffectEndDate: getEeffectEndDate,
        getDateOffset: getDateOffset,
        getEeffectDateOffset: getEeffectDateOffset,
        isWeekend: isWeekend,
        isLeapYear: isLeapYear,
        getLastDayOfMonth: getLastDayOfMonth,
        compareDate: compareDate,
        equalsIgnoreTime: equalsIgnoreTime,
        parseDate: parseDate
      };
    }
  ]);
})(angular);
